import {
    AwaitMatch,
    AwaitWhen,
    AwaitReport,
    DslAdapter,
    DslRule,
    RuleContext,
    NodePredicate,
    EngineCapability,
    DefineRuleContext,
    PropType,
    WhenResult,
    EngineCapabilityHint,
} from 'qvog-dsl';

import { InitialQuery, Query, Queryable, QueryDescriptor, ValuePredicate } from '~/core/dsl';
import { Value } from 'qvog-types';
import { getRandomString, QHint, QIssueColomns, QIssueTemplate, QRuleContext } from '~/adapter/Defines';
import { getEngineCapability } from '~/adapter/Capability';

type QueryableBuilderAction<C> = (context: C) => void;

class QueryableBuilder<C> {
    private name: string = '@unknown/custom-query/' + getRandomString(8);

    private readonly initAction: QueryableBuilderAction<C>;
    private actions: QueryableBuilderAction<C>[] = [];

    constructor(initAction: QueryableBuilderAction<C>) {
        this.initAction = initAction;
    }

    addAction(action: QueryableBuilderAction<C>): void {
        this.actions.push(action);
    }

    build(): DslRule<Queryable> {
        return (capability: EngineCapability): Queryable => {
            const context: QRuleContext = {
                ...capability,
            };
            this.initAction(context as C);

            const query: Query = (q: InitialQuery) => {
                context.native!.context.descriptor = q as QueryDescriptor;
                for (const action of this.actions) {
                    action(context as C);
                }
                return context.native!.context.descriptor;
            };
            return [context.options?.name || this.name, query];
        };
    }
}

class AwaitMatchAdapter<H extends QHint, C extends QRuleContext> implements AwaitMatch<H, C> {
    private readonly builder: QueryableBuilder<C>;

    constructor(builder: QueryableBuilder<C>) {
        this.builder = builder;
    }

    match<T extends Value>(
        param: NodePredicate<T> | ((node: Value, context: C) => node is T)
    ): AwaitWhen<PropType<H, 'node'>, T, C> {
        if (typeof param === 'function') {
            this.matchWithAction(param);
        } else {
            this.matchWithPredicate(param);
        }
        return new AwaitThenAndReportAdapter<PropType<H, 'node'>, T, C>(this.builder);
    }

    private matchWithPredicate<T extends Value>(predicate: NodePredicate<T>): void {
        this.matchWithAction((node, _) => predicate.is(node));
    }

    private matchWithAction<T extends Value>(predicate: (node: Value, context: C) => node is T): void {
        this.builder.addAction((context) => {
            context.native!.api.nodes((value) => predicate(value, context as C), '__match__');
        });
    }
}

class AwaitThenAndReportAdapter<N, T, C extends QRuleContext> implements AwaitWhen<N, T, C>, AwaitReport<N, C> {
    private builder: QueryableBuilder<C>;

    constructor(builder: QueryableBuilder<C>) {
        this.builder = builder;
    }

    when(action: (node: T, context: C) => WhenResult<N>): AwaitReport<N, C> {
        this.builder.addAction((context) => {
            context.native!.context.descriptor.where((f) =>
                f.on('__match__').where(
                    ValuePredicate.of((v) => {
                        const result = action(v as T, context as C);
                        if (result === undefined || result === null) {
                            return false;
                        } else if (typeof result === 'boolean') {
                            return result;
                        } else {
                            return true;
                        }
                    })
                )
            );
        });
        return this;
    }

    report(param: ((node: N, context: C) => void) | QIssueTemplate | QIssueColomns): DslRule<Queryable> {
        if (typeof param === 'function') {
            throw new Error('Report with action not supported in QVoG');
        } else if ('table' in param) {
            this.reportColumns(param);
        } else {
            this.reportImpl(param);
        }
        return this.builder.build();
    }

    private reportImpl(template: QIssueTemplate): DslRule<Queryable> {
        this.builder.addAction((context) => {
            context.native!.context.descriptor.select('__match__', template.message);
        });

        return this.builder.build();
    }

    private reportColumns(table: QIssueColomns): DslRule<Queryable> {
        this.builder.addAction((context) => {
            context.native!.context.descriptor.selectTable(table.table);
        });
        return this.builder.build();
    }
}

function entry<P, H extends EngineCapabilityHint, U extends DefineRuleContext<P, H>>(
    init: (context: RuleContext) => P
): AwaitMatch<H, U> {
    const builder = new QueryableBuilder<U>((context) => {
        context.props = init(context);
    });
    return new AwaitMatchAdapter(builder);
}

export class QAdapter extends DslAdapter<Queryable> {
    public constructor() {
        super();
    }

    entry = entry;

    loadDslRule(dslRule: DslRule<Queryable>): Queryable {
        return dslRule(getEngineCapability());
    }
}

export const adapter = new QAdapter();
